package cbs.jdtag.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import cbs.jdtag.AppContext;
import cbs.jdtag.DataModel2;
import cbs.jdtag.DataModel2.Alignment;
import cbs.jdtag.DataModel2.Token;
import cbs.jdtag.DataModel2.TokenFile;

public class XMLReader {

	private static final Logger LOGGER = Logger.getLogger(XMLReader.class.getName());
	private static final DocumentBuilderFactory DOMFACTORY = DocumentBuilderFactory.newInstance();
	private static DocumentBuilder builder;
	
	static {
		try {
			builder = DOMFACTORY.newDocumentBuilder();
		}
		catch (ParserConfigurationException e) {
			throw new RuntimeException(e);
		}
	}
	
	private XMLReader() { }
	
	public static void parseATAGFile(URI alignmentFile) throws MalformedURLException, SAXException, IOException, XMLException, URISyntaxException {
		DataModel2 dm = AppContext.getDM();
		
		LOGGER.log(Level.INFO, "Loading atag file \"{0}\" with encoding \"{1}\"...", new Object[]{ dm.getAlignmentFile(), dm.getEncoding() });
		InputSource source = new InputSource(new FileInputStream(alignmentFile.getPath()));
		source.setEncoding(dm.getEncoding());
		Document projectFile = builder.parse(source);
		Element root = projectFile.getDocumentElement();
		
		NodeList tokenFiles = root.getElementsByTagName("alignFile");
		if (tokenFiles.getLength() < 2) {
			throw new XMLException("There is at least one token file missing!");
		}
		else if (tokenFiles.getLength() > 2) {
			LOGGER.warning("Only two token files are supported, the others will be ignored.");
		}

		URI baseDir = new File(alignmentFile).getParentFile().toURI();
		URI tokenFile;
		Element e;
		String sign;
		
		// parse src token file
		e = (Element) tokenFiles.item(0);
		String srcKey = e.getAttribute("key");
		dm.addKey(srcKey);
		tokenFile = new URI(e.getAttribute("href"));
		sign = e.getAttribute("sign");
		if (tokenFile.getPath().indexOf('/') > -1) {
			parseTokenFile(baseDir.resolve(tokenFile), srcKey, sign);
		}
		else {
			parseTokenFile(baseDir.resolve("./" + tokenFile), srcKey, sign);
		}
		
		// parse tgt token file
		e = (Element) tokenFiles.item(1);
		String tgtKey = e.getAttribute("key");
		dm.addKey(tgtKey);
		tokenFile = new URI(e.getAttribute("href"));
		sign = e.getAttribute("sign");
		if (tokenFile.getPath().indexOf('/') > -1) {
			parseTokenFile(baseDir.resolve(tokenFile), tgtKey, sign);
		}
		else {
			parseTokenFile(baseDir.resolve("./" + tokenFile), tgtKey, sign);
		}
		
		// parse alignments
		NodeList alignList = root.getElementsByTagName("align");
		List<Alignment> alignments = new ArrayList<Alignment>();
		for (int i = 0; i < alignList.getLength(); ++i) {
			
			e = (Element) alignList.item(i);
			Alignment a = dm.new Alignment();
			
			String out[] = e.getAttribute("out").split(srcKey);
			for (int k = 1; k < out.length; ++k) {
				Token t = dm.getTokenById(srcKey, Integer.valueOf(out[k].trim()));
				a.addSrcToken(t);
				t.alignments.add(a);
			}

			String in[] = e.getAttribute("in").split(tgtKey);
			for (int k = 1; k < in.length; ++k) {
				Token t = dm.getTokenById(tgtKey, Integer.valueOf(in[k].trim()));
				a.addTgtToken(t);
				t.alignments.add(a);
			}
			
			a.setCreator(e.getAttribute("creator"));
			a.setType(e.getAttribute("type"));
			
			alignments.add(a);
		}
		dm.setAlignments(alignments);
	}
	
	public static void parseTokenFile(URI tokenFile, String key, String sign) throws MalformedURLException, SAXException, IOException {
		DataModel2 dm = AppContext.getDM();
		
		TokenFile tf = dm.new TokenFile();
		tf.key = key;
		tf.href = new File(tokenFile);
		tf.sign = sign;
		dm.tokenFiles.put(key, tf);
		
		LOGGER.log(Level.INFO, "Loading token file \"{0}\" with encoding \"{1}\"...", new Object[]{ tokenFile, dm.getEncoding() });
		InputSource source = new InputSource(new FileInputStream(tokenFile.getPath()));
		source.setEncoding(dm.getEncoding());
		Document tokenDoc = PosXmlReader.read(source);
		Element root = tokenDoc.getDocumentElement();
		
		// parse the token file, read every token
		LinkedHashMap<Integer, Token> tokensByPos = new LinkedHashMap<Integer, Token>();
		LinkedHashMap<Integer, Token> tokensById = new LinkedHashMap<Integer, Token>();
		NodeList wList = root.getElementsByTagName("W");
		Integer pos = 0;
		for (int i = 0; i < wList.getLength(); ++i) {
			
			Element e = (Element) wList.item(i);
			Token t = dm.new Token();
			
			t.id = Integer.valueOf(e.getAttribute("id"));
			t.pos = pos;
			t.lineNumber = Integer.valueOf((String)e.getUserData("lineNumber"));
			t.key = key;
			t.text = e.getTextContent();
			
			tokensByPos.put(t.pos, t);
			tokensById.put(t.id, t);
			++pos;
		}
		
		dm.addSideByPos(key, tokensByPos);
		dm.addSideById(key, tokensById);
	}
}
